/**
 * \file Listorifier.cpp
 *
 * Asteriskos Compiler 1
 * Written 2010-2011, by Anthony Berlin and Alexander Carlqvist
 *
 * This file is part of ICC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * \todo Go through \c build_expr() and see if we need to take any actions
 *   when converting expressions to LISTOR. One thing that MAY cause problems
 *   is the casting-expressions, but since all types that we support in
 *   Asteriskos are also supported by LISTOR, we could be find. Examine this
 *   further.
 */

#include "Listorifier.h"

#define SKIP_PARSER_IN_SEMANTICS_H
#include "semantics.h"
#undef SKIP_PARSER_IN_SEMANTICS_H

#include <Exception/Exception.h>
#include <boost/lexical_cast.hpp>
#include <fstream>

///
/// \brief Constructors.
/// \param moduleList List of imported modules to listorify.
/// \param ast The Abstract Syntax Tree to listorify.
/// \param globalScope The global \c SymbolTable created in the parsing phase.
/// \param mainDeclTree If \c main was declared during parsing, pass that \c Tree to this parameter.
///
Listorifier::Listorifier(list<Module*>& moduleList, Tree* ast, SymbolTable* globalScope,
                         Tree* mainDeclTree)
    : m_moduleList(moduleList), m_ast(ast), m_globalScope(globalScope),
        m_indentDepth(0), m_mainDeclTree(mainDeclTree)
{
}

Listorifier::Listorifier()
    : m_moduleList(NULL), m_ast(NULL_TREE), m_globalScope(NULL),
        m_indentDepth(0), m_mainDeclTree(NULL_TREE)
{
}

///
/// \brief Rearrange the AST so that it represents a LISTOR tree.
/// \note Also includes the ASTs from all imported modules.
///
void Listorifier::rearrange_ast()
{
    if (m_ast->m_type != TREE_DECLARATION_SEQ)
        throw new Exception("Listorifier::rearrange_ast(): expected declaration-sequence", 20);

    // Create the global code block.
    m_listorTree = new Tree(NULL, NULL, TREE_CODE_BLOCK);

    // Include the declarations of any imported module.
    foreach (Module* m, m_moduleList)
        if (m->m_ast)
            walk_cmp_stmt(m->m_ast, m_listorTree);

    // Walk and convert the AST to its LISTOR equivalent.
    walk_cmp_stmt(m_ast, m_listorTree);
}

///
/// \brief Walk an Asteriskos compound-statement \c Tree and convert it to a LISTOR \c Tree.
/// \param cmpStmt The Asteriskos compound-statement \c Tree to walk.
/// \param listorTree The LISTOR \c Tree node to push all the statements onto.
///
void Listorifier::walk_cmp_stmt(Tree* cmpStmt, Tree* listorTree)
{
    list<Tree*> funcDecls;
    list<Tree*> funcDefs;
    list<Tree*> varDecls;
    list<Tree*> statements;

    bool inGlobalScope = false;

    if (cmpStmt->m_type == TREE_DECLARATION_SEQ)
        inGlobalScope = true;

    /*
     * Go through all siblings in the compound-statement and sort the siblings in the correct order
     * for the LISTOR tree (declarations and function-definitions are always found before statements
     * in LISTOR code).
     */

    foreach (Tree* sibling, *cmpStmt->m_stack)
    {
        switch (sibling->m_type)
        {
            case TREE_FUNC_DECL:
                funcDecls.push_back(sibling);
                break;
            case TREE_FUNC_DEF:
                if (inGlobalScope && sibling->get_node(2)->m_token->m_lexeme == "main" &&
                    m_mainDeclTree != NULL_TREE)
                {
                    funcDecls.push_front(m_mainDeclTree);
                }
                funcDefs.push_back(sibling);
                break;
            case TREE_VAR_DECL:
                varDecls.push_back(sibling);

                // If there is an assignment expression in there, put that in a seperate statement.
                if (sibling->back()->m_type == TREE_ASSIGN_EXPR)
                {
                    statements.push_back(sibling->back());
                    sibling->push(sibling->back()->front());
                    sibling->pop(sibling->back());
                }

                break;
            default:
                statements.push_back(sibling);
        }
    }

    /*
     * Now we will walk through the statements, arranging the LISTOR tree as we go. First we walk
     * through all function-declarations, then all the function-definitions, then all the variable-
     * declarations, and then finally all the rest of the statements.
     */

    // Function-declarations.
    foreach (Tree* sibling, funcDecls)
        listorTree->push(sibling);

    // Function-definitions.
    foreach (Tree* sibling, funcDefs)
    {
        listorTree->push(sibling);
        Tree* funcBody = sibling->back();
        if (funcBody->m_type == TREE_CMP_STMT)
        {
            sibling->push(new Tree(NULL, NULL, TREE_CODE_BLOCK));
            walk_cmp_stmt(funcBody, sibling->back());
            sibling->pop(funcBody);
        }
    }

    // Variable-declarations.
    foreach (Tree* sibling, varDecls)
        listorTree->push(sibling);

    // Misc statements.
    Tree* tree = NULL_TREE;

    foreach (Tree* sibling, statements)
    {
        tree = build_stmt(sibling);

        if (tree == NULL_TREE)
            continue;

        listorTree->push(tree);
    }
}

///
/// \brief Build a LISTOR statement \c Tree from an Asteriskos statement.
///
/// This function basically does some necessary changes to the given \c Tree for the \c put-
/// \c statement, \c compound-statement, and \c some expressions.
///
/// \param tree The Asteriskos statement \c Tree.
/// \return The built LISTOR statement \c Tree.
///
Tree* Listorifier::build_stmt(Tree* tree)
{
    Tree* listorTree = NULL_TREE;

    switch (tree->m_type)
    {
        case TREE_PUT_STMT:
            listorTree = NULL_TREE;
            break;
        case TREE_CMP_STMT:
            listorTree = new Tree(NULL, NULL, TREE_CODE_BLOCK);
            walk_cmp_stmt(tree, listorTree);
            break;
        case EXPR_TREE_TYPES:
            listorTree = build_expr(tree);
            break;
        default:
            listorTree = tree;
    }

    return listorTree;
}

///
/// \brief Build a LISTOR expression \c Tree from an Asteriskos expression.
/// \param tree The Asteriskos expression \c Tree.
/// \return The built LISTOR expression \c Tree.
///
Tree* Listorifier::build_expr(Tree* expr)
{
    if (expr == NULL_TREE)
        return NULL_TREE;

    Tree* listorTree = NULL_TREE;

    switch (expr->m_type)
    {
        case TREE_ASSIGN_EXPR:
            listorTree = expr;
            break;
        case TREE_CASTING_EXPR:
            listorTree = expr;
            break;
        case TREE_ARITHM_EXPR:
            listorTree = expr;
            break;
        case TREE_EQUALITY_EXPR:
            listorTree = expr;
            break;
        case TREE_FUNC_CALL:
            listorTree = expr;
            break;
        case TREE_IDENT:
            listorTree = expr;
            break;
        case TREE_ID_EXPR:
            listorTree = expr;
            break;
        case TREE_LITERAL:
            listorTree = expr;
            break;
        case TREE_LOGICAL_AND_EXPR:
            listorTree = expr;
            break;
        case TREE_LOGICAL_NOT_EXPR:
            listorTree = expr;
            break;
        case TREE_LOGICAL_OR_EXPR:
            listorTree = expr;
            break;
        case TREE_POLARITY_EXPR:
            listorTree = expr;
            break;
        case TREE_PRIMARY_EXPR:
            listorTree = expr;
            break;
        case TREE_RELATIONAL_EXPR:
            listorTree = expr;
            break;
        default:
            throw new Exception(
                "Listorifier::build_expr(): unknown expression type encountered", 20);
    }

    return listorTree;
}

///
/// \brief Generate code from the LISTOR \c Tree.
/// \param codeBlock The LISTOR code block \c Tree to convert to code.
///
void Listorifier::generate_code(Tree* codeBlock, bool forceBraces)
{
    string line;
    list<Tree*> treeList;
    bool cmpStmt = true;
    string exprStr;

    // See if this is a code block (compound-statement) or not.
    switch (codeBlock->m_type)
    {
        case TREE_CODE_BLOCK:
        case TREE_CMP_STMT:
            break;
        default:
            cmpStmt = false;
    }

    // If this is a code block, we want to put all the statements inside of it into the treeList
    // so that we can generate code for each one of them.
    if (cmpStmt)
    {
        foreach (Tree* sibling, *codeBlock->m_stack)
            treeList.push_back(sibling);
    }
    // If this is just a statement, we only put that one in the treeList.
    else
        treeList.push_back(codeBlock);

    if (cmpStmt || forceBraces)
    {
        line = indentation() + "{";
        m_outputLines.push_back(line);
        ++m_indentDepth;
    }

    // Go through all the tree nodes in treeList and generate code from those.
    foreach (Tree* sibling, treeList)
    {
        switch (sibling->m_type)
        {
            case TREE_FUNC_DECL:
                line = indentation() + convert_datatype_instance_to_listor_datatype_string(
                            sibling->back()->m_datatype) + " ";

                if (sibling->back()->m_token->m_lexeme == "main" && codeBlock == m_listorTree)
                    line += "export ";

                else if (sibling->get_node(2)->m_type == TREE_MODIFIER_LIST)
                {
                    bool isImported = false;

                    foreach (Tree* modifier, *sibling->get_node(2)->m_stack)
                    {
                        if (modifier->m_rid == RID_EXTERN)
                        {
                            isImported = true;
                            line += "import ";
                        }
                        else if (modifier->m_rid == RID_STATIC)
                            isImported = true;
                    }

                    if (!isImported)
                        line += "export ";
                }
                else
                    line += "export ";

                line += sibling->back()->m_token->m_lexeme;
                line += "(";
                line += convert_param_tree_to_listor_string(sibling->front()->back());
                line += ") cdecl;";

                m_outputLines.push_back(line);

                break;
            case TREE_FUNC_DEF:
                line = indentation() + sibling->get_node(2)->m_token->m_lexeme;
                line += "(";
                line += ")";

                m_outputLines.push_back(line);
                generate_code(sibling->back(), true);

                break;
            case TREE_VAR_DECL:
                line = indentation() + convert_datatype_instance_to_listor_datatype_string(
                            sibling->get_node(2)->front()->m_datatype) + " ";
                line += sibling->back()->front()->m_token->m_lexeme;
                line += ";";

                m_outputLines.push_back(line);

                break;
            case EXPR_TREE_TYPES:
                exprStr = "";
                semantics::convert_expr_to_string(sibling, exprStr, true);

                line = indentation() + exprStr;
                line += ";";

                m_outputLines.push_back(line);

                break;
            case TREE_IF_STMT:
                exprStr = "";
                semantics::convert_expr_to_string(sibling->front(), exprStr, true);

                line = indentation() + "if ";
                line += "(";
                line += exprStr;

                if (sibling->front()->m_type == TREE_IDENT
                || sibling->front()->m_type == TREE_FUNC_CALL)
                    line += " >= 1";

                line += ")";

                m_outputLines.push_back(line);
                generate_code(sibling->get_node(2), true);

                if (sibling->m_stack->size() >= 3)
                {
                    line = indentation() + "else ";
                    m_outputLines.push_back(line);
                    generate_code(sibling->back()->front(), true);
                }

                break;
            case TREE_WHILE_STMT:
                exprStr = "";
                semantics::convert_expr_to_string(sibling->front(), exprStr, true);

                line = indentation() + "while ";
                line += "(";
                line += exprStr;

                if (sibling->front()->m_type == TREE_IDENT
                || sibling->front()->m_type == TREE_FUNC_CALL)
                    line += " >= 1";

                line += ")";

                m_outputLines.push_back(line);
                generate_code(sibling->back(), true);

                break;
            case TREE_RETURN_STMT:
                exprStr = "";
                semantics::convert_expr_to_string(sibling->front(), exprStr, true);

                line = indentation() + "return";

                if (exprStr != "")
                    line += " " + exprStr;

                line += ";";

                m_outputLines.push_back(line);

                break;
            case TREE_CONTINUE_STMT:
                line = indentation() + "continue;";
                m_outputLines.push_back(line);
                break;
            case TREE_BREAK_STMT:
                line = indentation() + "break;";
                m_outputLines.push_back(line);
                break;
            default:
                break;
        }
    }

    if (cmpStmt || forceBraces)
    {
        --m_indentDepth;
        line = indentation() + "}";
        m_outputLines.push_back(line);
    }
}

///
/// \brief Convert an Asteriskos \c Datatype to a LISTOR datatype represented as a \c string.
/// \param datatype The Asteriskos datatype \c Tree to convert.
/// \return The converted \c string.
///
string Listorifier::convert_datatype_instance_to_listor_datatype_string(Datatype* datatype)
{
    if (datatype == NULL)
        return "error";

    string result;

    if (datatype->get_flags() & DT_VOID)
        result = "void";
    else if (datatype->get_flags() & DT_BOOL)
        result = "u_int8";
    else if (datatype->get_flags() & DT_INT)
        result = "_int32";
    else if (datatype->get_flags() & DT_STRING)
        result = "string8";

    if (datatype->get_flags() & SPECF_CONST)
        result = (string)"const " + result;
    if (datatype->get_flags() & SPECF_SIGNED)
        result = (string)"s" + result;
    else if (datatype->get_flags() & SPECF_UNSIGNED)
        result = (string)"u" + result;

    return result;
}

///
/// \brief Convert an Asteriskos \c parameter \c Tree to a LISTOR parameter represented
///   as a \c string.
/// \param paramTree The Asteriskos parameter \c Tree to convert.
/// \return The converted \c string.
///
string Listorifier::convert_param_tree_to_listor_string(Tree* paramTree)
{
    if (paramTree == NULL_TREE ||
        (paramTree->m_type != TREE_PARAM_DECL_LIST &&
        paramTree->m_type != TREE_PARAM_TYPE_LIST))
            return "error";

    string result;

    bool first = true;

    foreach (Tree* param, *paramTree->m_stack)
    {
        if (first)
            first = false;
        else
            result += ", ";

        result += convert_datatype_instance_to_listor_datatype_string(param->back()->m_datatype);
        result += (string)" " + param->back()->m_token->m_lexeme;
    }

    return result;
}

///
/// \brief Returns a string of tabs, for the indentation of the LISTOR code (based on the value of
///   \c m_indentDepth.
/// \return A string containing the indentation.
///
string Listorifier::indentation()
{
    string result;

    for (unsigned int i = 0; i < m_indentDepth; ++i)
        result += "    ";

    return result;
}

///
/// \brief Get the LISTOR \c Tree.
/// \return The LISTOR \c Tree.
///
Tree* Listorifier::get_listor_tree()
{
    return m_listorTree;
}

///
/// \brief Output the generated code to a file.
/// \param path The path to the file to output to.
///
void Listorifier::output_code(string path)
{
    std::ofstream fileStream(path.c_str());

    if (!fileStream || !fileStream.is_open())
        throw new Exception("Failed to write LISTOR code to disc.", 50);

    foreach (string line, m_outputLines)
        fileStream << line << "\n";

    fileStream.close();
}

///
/// \brief Convert this \c Listorifier to a \c string.
/// \return A \c string representing the \c Listorifier.
///
string Listorifier::to_string() const
{
    return (string)"<" + to_chars() + " : " + boost::lexical_cast<string>(this) + ">\n";
}

///
/// \brief Convert this \c Listorifier to a string of \c char*.
/// \return A \c char* string representing the \c Listorifier.
///
char* Listorifier::to_chars() const
{
    return (char*)"Listorifier";
}

///
/// \brief Print a text version of this \c Listorifier.
///
void Listorifier::print() const
{
    std::cout << to_string();
}
